/*=========================================================================================================================/
 * 
 * FILE: Player.cs
 * DATE: 10/29/2011
 * CHANGE: 11/30/2011
 * 
 * ADDED:
 *          - Collision detection/destruction for Drillable tiles
 * 
 *========================================================================================================================
 *
 * BASED ON:
 * 
// Player.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------*/

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Homebound
{
    class Player
    {
        // Animations
        private Animation idleAnimation;
        private Animation runAnimation;
        private Animation jumpAnimation;
        private Animation celebrateAnimation;
        private Animation dieAnimation;
        private SpriteEffects flip = SpriteEffects.None;
        private AnimationPlayer sprite;

        // Weapons
        public Weapons weapons;

        // Sounds
        private SoundEffect killedSound;
        private SoundEffect jumpSound;
        private SoundEffect fallSound;

        // Player has been hit
        public bool isHit = false;
        private bool visible = true;
        private TimeSpan hitTimer;
        private double visibleTimer;
        private int flashingSpeed = 100;

        // Player has energy variable which is used with power-ups. This is a float value to be clamped between 0f (energy depleted) and 1f (full energy.)
        // It automatically replenishes (function of time.)
        private float currentEnergyLevel = 1.0f;

        // Boolean values that indicate whether we should wait for energy to replenish to a certain level before using it.
        private bool waitingForEnergy = false;          // This one works for all abilities that use energy in a contiuous manner.
        private bool waitingForDoubleJumpEnergy = false;   // Double jumping is special because it uses energy one big chunk at a time.


        //// Are demands being placed on available energy?
        //private bool usingEnergy = false;

        // This property exposes the amount of energy left in the player to the rest of the project.
        public float CurrentEnergy {
            get { return currentEnergyLevel; }
        }

        // Boolean values indicating whether a certain skill (power-up) is enabled or not.
        private bool canDoubleJump = false;
        private bool canSpeeUp = false;
        public bool canDrill = true;
        public bool canHammer = true;
        public bool canLaser = true;

        // Boolean values indicating whether a certain power-up is being applied or not.
        private bool isDoubleJumping = false;
        private bool isSpeedingUp = false;
        private bool isDrilling = false;
        
        // Matthew: is currently drilling through a tile
        private bool isDrillingTile = false;
        private Vector2 drillingStartPos;
        private bool isDrillingRight = false;

        // Properties related to the power-ups.
        public bool CanDoubleJump {
            get { return canDoubleJump; }
            set { canDoubleJump = value; }
        }

        public bool CanSpeedUp
        {
            get { return canSpeeUp; }
            set { canSpeeUp = value; }
        }

        public bool CanDrill
        {
            get { return canDrill; }
            set { canDrill = value; }
        }

        // Player takes damage after enemy attack
        public void takeDamage()
        {
            isHit = true;

            // Set timer
            hitTimer = TimeSpan.FromSeconds(2);
        }

        public Level Level
        {
            get { return level; }
        }
        Level level;

        public bool IsAlive
        {
            get { return isAlive; }
        }
        bool isAlive;

        // Physics state
        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }
        Vector2 position;

        private float previousBottom;

        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }
        Vector2 velocity;

        // Constants for controling horizontal movement
        private const float MoveAcceleration = 13000.0f;
        private const float MaxMoveSpeed = 1750.0f;
        private const float GroundDragFactor = 0.48f;
        private const float AirDragFactor = 0.58f;

        // Matthew: max speed when drilling
        private const float maxDrillSpd = MaxMoveSpeed / 50.0f;

        // Matthew: slide factor for when isRunning = false
        private float slideDragFactor = 0.90f;

        // Matthew: Indicates whether player is currently inputting a running command
        private bool isRunningLeft = false;
        private bool isRunningRight = false;

        // Power-up related constants.
        private const float SpeedUpMultiplier = 2.0f;               // Factor by which to increase velocity when the speed-up power-up is in use.
        private const float SpeedUpEnergyConsumptionRate = 0.09f;   // Units of energy per second.
        private const float DoubleJumpEnergyUsage = 0.2f;           // Units of energy that are used when double jumping. This is not a rate.

        private const float EnergyReplenishRate = 0.1f;             // Rate at which to replenish energy when not in use. Units per second.

        // Constants for controlling vertical movement
        private const float MaxJumpTime = 0.35f;
        private const float JumpLaunchVelocity = -3500.0f;
        private const float GravityAcceleration = 3400.0f;
        private const float MaxFallSpeed = 550.0f;
        private const float JumpControlPower = 0.14f; 

        // Input configuration
        private const float MoveStickScale = 1.0f;
        private const float AccelerometerScale = 1.5f;
        private const Buttons JumpButton = Buttons.A;


        /// <summary>
        /// Gets whether or not the player's feet are on the ground.
        /// </summary>
        public bool IsOnGround
        {
            get { return isOnGround; }
        }
        bool isOnGround;

        /// <summary>
        /// Current user movement input.
        /// </summary>
        public float movement;

        

        // Jumping state
        private bool isJumping;
        private bool wasJumping;
        private float jumpTime;
        private bool allowDoubleJump = false;

        // If true, indicates the player has already double jumped. Therefore, the ability to double jump is restored only upon contact with the ground again.
        // If false, the double jump ability is waiting to be used.
        private bool waitingForGroundBeforeDoubleJump = false;

        private Rectangle localBounds;
        /// <summary>
        /// Gets a rectangle which bounds this player in world space.
        /// </summary>
        public Rectangle BoundingRectangle
        {
            get
            {
                int left = (int)Math.Round(Position.X - sprite.Origin.X) + localBounds.X;
                int top = (int)Math.Round(Position.Y - sprite.Origin.Y) + localBounds.Y;

                return new Rectangle(left, top, localBounds.Width, localBounds.Height);
            }
        }

        // Player life
        public int life, initLife = 100;
        public int lives;
        const int INIT_LIVES = 3;

        // Player physics.
        float inertia;

        // Player kill counter (for enemy kills)
        public int killCount = 0;

        /// <summary>
        /// Constructs a new player.
        /// </summary>
        public Player(Level level, Vector2 position)
        {
            this.level = level;
            lives = INIT_LIVES;

            LoadContent();
            weapons = new Weapons(this.level, this);

            Reset(position);
        }

        /// <summary>
        /// Loads the player sprite sheet and sounds.
        /// </summary>
        public void LoadContent()
        {
            // Load animated textures.
            idleAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Robot_idle_small"), 0.1f, true);
            runAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/run_sequence"), 0.1f, true);
            jumpAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/jump_sequence"), 0.1f, false);
            celebrateAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Celebrate"), 0.1f, false);
            dieAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Die"), 0.1f, false);

            // Calculate bounds within texture size.            
            int width = (int)(idleAnimation.FrameWidth * 0.4);
            int left = (idleAnimation.FrameWidth - width) / 2;
            int height = (int)(idleAnimation.FrameWidth * 0.8);
            int top = idleAnimation.FrameHeight - height;
            localBounds = new Rectangle(left, top, width, height);

            // Load sounds.            
            killedSound = Level.Content.Load<SoundEffect>("Sounds/robot_death");
            jumpSound = Level.Content.Load<SoundEffect>("Sounds/jump");
            fallSound = Level.Content.Load<SoundEffect>("Sounds/crash");
        }

        /// <summary>
        /// Resets the player to life.
        /// </summary>
        /// <param name="position">The position to come to life at.</param>
        public void Reset(Vector2 position)
        {
            Position = position;
            Velocity = Vector2.Zero;
            isAlive = true;

            // Reset life total
            lives--;

            // Reset HP
            life = initLife;

            // Set to idel animation
            sprite.PlayAnimation(idleAnimation);
        }

        /// <summary>
        /// Handles input, performs physics, and animates the player sprite.
        /// </summary>
        /// <remarks>
        /// We pass in all of the input states so that our game is only polling the hardware
        /// once per frame. We also pass the game's orientation because when using the accelerometer,
        /// we need to reverse our motion when the orientation is in the LandscapeRight orientation.
        /// </remarks>

        public void Update(
            GameTime gameTime, 
            KeyboardState keyboardState, 
            GamePadState gamePadState)
        {

            // IF player has been hit, update timer
            if (isHit)
            {
                // Update timer
                hitTimer -= gameTime.ElapsedGameTime;
                visibleTimer += gameTime.ElapsedGameTime.TotalMilliseconds;

                // Toggle visibility
                if (visibleTimer > flashingSpeed)
                {
                    visible = !visible;
                    visibleTimer = 0;
                }

                if (hitTimer.Ticks <= 0)
                {
                    // No longer hit
                    isHit = false;

                    // Fully visible
                    visible = true;

                }
            }

            // Matthew: IF player is drilling a tile, check to see if he has passed through the entire tile, or stopped moving
            if (isDrillingTile)
            {
                // Matthew: Drilling right && through the tile
                if (isDrillingRight && (position.X >= (drillingStartPos.X + Tile.Width)))
                {
                    isDrillingTile = false;
                }

                // Matthew: Drilling left && through the tile
                else if (!isDrillingRight && (position.X <= (drillingStartPos.X - Tile.Width)))
                {
                    isDrillingTile = false;
                }

                // Matthew: Not actively running right or left
                else if ( (!isRunningRight) && (!isRunningLeft) )
                {
                    isDrillingTile = false;
                }
            }

            GetInput(keyboardState, gamePadState);

            ApplyPhysics(gameTime);

            UpdateEnergy(gameTime);

            if (IsAlive && IsOnGround)
            {
                if (Math.Abs(Velocity.X) - 0.02f > 0)
                {
                    sprite.PlayAnimation(runAnimation);
                }
                else
                {
                    sprite.PlayAnimation(idleAnimation);
                }
            }

            // Clear input.
            movement = 0.0f;
            isJumping = false;

            // Update Weapons
            weapons.Update(gameTime);
        }

        /// <summary>
        /// Gets player horizontal movement and jump commands from input.
        /// </summary>
        private void GetInput(
            KeyboardState keyboardState, 
            GamePadState gamePadState)
        {

            // Get analog horizontal movement.
            movement = gamePadState.ThumbSticks.Left.X * MoveStickScale;

            // Ignore small movements to prevent running in place.
            if (Math.Abs(movement) < 0.5f)
                movement = 0.0f;

            // If any digital horizontal movement input is found, override the analog movement.
            if (gamePadState.IsButtonDown(Buttons.DPadLeft) ||
                keyboardState.IsKeyDown(Keys.Left) ||
                keyboardState.IsKeyDown(Keys.A))
            {
                movement = -1.0f;

                isRunningLeft = true;
                isRunningRight = false;
            }
            else if (gamePadState.IsButtonDown(Buttons.DPadRight) ||
                     keyboardState.IsKeyDown(Keys.Right) ||
                     keyboardState.IsKeyDown(Keys.D))
            {
                movement = 1.0f;

                isRunningLeft = false;
                isRunningRight = true;
            }
            else
            {
                isRunningLeft = false;
                isRunningRight = false;
            }

            // While the buttons are up decrease velocity.
            if (gamePadState.IsButtonUp(Buttons.DPadLeft) ||
                keyboardState.IsKeyUp(Keys.Left) ||
                keyboardState.IsKeyUp(Keys.A))
            {
                if (inertia > 0) // The player still has inertia.
                {
                    //inertia -= ela
                }
            }
            else if (gamePadState.IsButtonUp(Buttons.DPadRight) ||
                     keyboardState.IsKeyUp(Keys.Right) ||
                     keyboardState.IsKeyUp(Keys.D))
            {
                movement = 1.0f;
            }

            // Determine whether the player is using the speed-up power-up.
            if ((keyboardState.IsKeyDown(Keys.LeftShift) || gamePadState.Buttons.RightShoulder == ButtonState.Pressed) && !waitingForEnergy)
                isSpeedingUp = true;
            else
                isSpeedingUp = false;

            // Determine whether the player is using the drill
            if
            (
                weapons.type == Weapons.WeaponType.Drill
                && weapons.active
            )
            {
                isDrilling = true;
            }
            else
            {
                isDrilling = false;
            }

            // Check if the player wants to jump.
            isJumping =
                gamePadState.IsButtonDown(JumpButton) ||
                keyboardState.IsKeyDown(Keys.Space) ||
                keyboardState.IsKeyDown(Keys.Up) ||
                keyboardState.IsKeyDown(Keys.W);

            // If the player was in the ascent of a jump in a previous frame and uses the jump key in this frame, the player is double jumping.
            if (isJumping && allowDoubleJump)
                isDoubleJumping = true;
            else
                isDoubleJumping = false;

            // The player is allowed to double jump if he is not pressing the jump button and he is currently in the air. Also,
            // double jumping is subject to whether this ability is enabled and available energy allows for this. Finally, the player can only
            // use double jumping once before contact with the ground occurs again.
            if (!isJumping && canDoubleJump && !waitingForDoubleJumpEnergy && !waitingForGroundBeforeDoubleJump && Velocity.Y != 0f) // Velocity.Y < 0f && 
                allowDoubleJump = true;
            else
                allowDoubleJump = false;

        }

        /// <summary>
        /// Updates the player's velocity and position based on input, gravity, etc.
        /// </summary>
        public void ApplyPhysics(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            inertia = velocity.X;

            Vector2 previousPosition = Position;

            float speedUpFactor = 1f;

            if (isSpeedingUp && CanSpeedUp) // TODO May want to move testing CanSpeedUp to GetInput() for consistency (we're already testing CanDoubleJump there - these are all player ability boolean values).
                speedUpFactor = SpeedUpMultiplier;

            // Base velocity is a combination of horizontal movement control and
            // acceleration downward due to gravity.
            velocity.X += movement * MoveAcceleration * speedUpFactor * elapsed;
            velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);

            velocity.Y = DoJump(velocity.Y, gameTime);

            if (isDoubleJumping) waitingForGroundBeforeDoubleJump = true;   // We are not double jumping again until back on the ground.

            // Apply pseudo-drag horizontally.
            if (IsOnGround)
            {
                // Matthew: let slide if not actively running
                if (isRunningLeft || isRunningRight)
                {
                    velocity.X *= GroundDragFactor;
                }
                else
                {
                    velocity.X *= slideDragFactor;
                }
            }
            else
                velocity.X *= AirDragFactor;

            // Prevent the player from running faster than his top speed.
            // Matthew: top speed is slower when drilling through a tile
            if ( isDrillingTile )
            {
                velocity.X = MathHelper.Clamp(velocity.X, -maxDrillSpd, maxDrillSpd);
            }
            else
            {
                velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);
            }

            // Apply velocity when running
            Position += velocity * elapsed;
            Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));

            // If the player is now colliding with the level, separate them.
            HandleCollisions();

            if (IsOnGround) waitingForGroundBeforeDoubleJump = false;       // We can double jump again.

            // If the collision stopped us from moving, reset the velocity to zero.
            if (Position.X == previousPosition.X && (!isDrilling) )
                velocity.X = 0;

            if (Position.Y == previousPosition.Y)
                velocity.Y = 0;
        }

        /// <summary>
        /// Calculates the Y velocity accounting for jumping and
        /// animates accordingly.
        /// </summary>
        /// <remarks>
        /// During the ascent of a jump, the Y velocity is completely
        /// overridden by a power curve. During the descent, gravity takes
        /// over. The jump velocity is controlled by the jumpTime field
        /// which measures time into the ascent of the current jump.
        /// </remarks>
        /// <param name="velocityY">
        /// The player's current velocity along the Y axis.
        /// </param>
        /// <returns>
        /// A new Y velocity if beginning or continuing a jump.
        /// Otherwise, the existing Y velocity.
        /// </returns>
        private float DoJump(float velocityY, GameTime gameTime)
        {

            bool wasOnGround = isOnGround;  // Save the value of isOnGround to restore at the end of the method. This is because callers of this method do not expect it to modify this variable.

            if (isDoubleJumping)
            {
                // If double jumping, act as though we were on the ground. This effectively restarts the jumping sequence.
                jumpTime = 0f;
                isOnGround = true;
            }

            // If the player wants to jump
            if (isJumping)
            {

                //Console.WriteLine("Is Jumping");

                // Begin or continue a jump
                if ((!wasJumping && IsOnGround) || jumpTime > 0.0f)
                {
                    if (jumpTime == 0.0f)
                        jumpSound.Play();

                    jumpTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                    sprite.PlayAnimation(jumpAnimation);
                }

                // If we are in the ascent of the jump
                if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
                {
                    // Fully override the vertical velocity with a power curve. This gives players more control over the top of the jump.
                    velocityY = JumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));
                }
                else
                {
                    // Reached the apex of the jump
                    jumpTime = 0.0f;
                }
            }
            else
            {
                // Continues not jumping or cancels a jump in progress
                jumpTime = 0.0f;

            }
            wasJumping = isJumping;

            isOnGround = wasOnGround;   // Restore value of isOnGround (see wasOnGround declaration.)

            //Console.WriteLine("Jump Time: " + jumpTime + ", " + "Is on ground: " + IsOnGround + ", Was Jumping: " + wasJumping);

            return velocityY;
        }

        /// <summary>
        /// Updates energy level based on whether energy is currently in use (decreases it at the required rate) or not (increases it at a constant rate.)
        /// </summary>
        private void UpdateEnergy(GameTime gameTime){

            bool usingEnergy = false;

            // See if player is using the speed up ability. If yes, decrease available energy as per the energy consumption rate for this ability.
            if (currentEnergyLevel > 0f) {
                if (isSpeedingUp && CanSpeedUp) {   // TODO May want to move testing CanSpeedUp to GeInput() instead.
                    usingEnergy = true;
                    currentEnergyLevel -= SpeedUpEnergyConsumptionRate * (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
            }

            // Slash energy off for the double jump in one go, if a double jump is being applied right now.
            if (isDoubleJumping && !waitingForDoubleJumpEnergy) {
                usingEnergy = true;
                currentEnergyLevel -= DoubleJumpEnergyUsage;
            }

            // Replenish energy if not in use.
            if (!usingEnergy && currentEnergyLevel < 1f) {
                // Replenish at a constant rate. Also, update boolean values that indicate sufficient energy is available for the various abilities.
                currentEnergyLevel += EnergyReplenishRate * (float)gameTime.ElapsedGameTime.TotalSeconds;
                currentEnergyLevel = MathHelper.Clamp(currentEnergyLevel, 0f, 1f);
                if (currentEnergyLevel >= 0.1f) waitingForEnergy = false;
                else waitingForEnergy = true;       // Wait until energy has been replenished to 10% before using it again.
                if (currentEnergyLevel >= DoubleJumpEnergyUsage) waitingForDoubleJumpEnergy = false;
                else waitingForDoubleJumpEnergy = true;

            }

        }

        /// <summary>
        /// Detects and resolves all collisions between the player and his neighboring
        /// tiles. When a collision is detected, the player is pushed away along one
        /// axis to prevent overlapping. There is some special logic for the Y axis to
        /// handle platforms which behave differently depending on direction of movement.
        /// </summary>
        private void HandleCollisions()
        {
            // Get the player's bounding rectangle and find neighboring tiles.
            Rectangle bounds = BoundingRectangle;
            int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
            int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
            int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
            int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;

            // Matthew: Adjust left and right tiles for drilling
            if (weapons.type == Weapons.WeaponType.Drill && weapons.playerDirection == 1)
            {
                rightTile = (int)Math.Ceiling(((float)(bounds.Right + weapons.drillSize.X) / Tile.Width)) - 1;
            }
            else if (weapons.type == Weapons.WeaponType.Drill && weapons.playerDirection == -1)
            {
                leftTile = (int)Math.Ceiling((float)(bounds.Left - weapons.drillSize.X) / Tile.Width);
            }

            // Reset flag to search for ground collision.
            isOnGround = false;

            // For each potentially colliding tile,
            for (int y = topTile; y <= bottomTile; ++y)
            {
                for (int x = leftTile; x <= rightTile; ++x)
                {
                    // If this tile is collidable,
                    TileCollision collision = Level.GetCollision(x, y);

                    if (collision != TileCollision.Passable)
                    {
                        // Determine collision depth (with direction) and magnitude.
                        Rectangle tileBounds = Level.GetBounds(x, y);
                        Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);

                        // Matthew: Adjust collision depth for drilling
                        if (weapons.type == Weapons.WeaponType.Drill)
                        {
                            Rectangle newBounds = bounds;
                            newBounds.Width += (int)weapons.drillSize.X / 2;

                            depth = RectangleExtensions.GetIntersectionDepth(newBounds, tileBounds);
                        }

                        if (depth != Vector2.Zero)
                        {
                            float absDepthX = Math.Abs(depth.X);
                            float absDepthY = Math.Abs(depth.Y);

                            // Resolve the collision along the shallow axis.
                            if (absDepthY < absDepthX || collision == TileCollision.Platform)
                            {
                                // If we crossed the top of a tile, we are on the ground.
                                if (previousBottom <= tileBounds.Top)
                                    isOnGround = true;

                                // Ignore platforms, unless we are on the ground.
                                if (collision == TileCollision.Impassable || IsOnGround)
                                {
                                    // Resolve the collision along the Y axis.
                                    Position = new Vector2(Position.X, Position.Y + depth.Y);

                                    // Perform further collisions with the new bounds.
                                    bounds = BoundingRectangle;
                                }
                            }
                            else if (collision == TileCollision.Impassable) // Ignore platforms.
                            {
                                // Resolve the collision along the X axis.
                                Position = new Vector2(Position.X + depth.X, Position.Y);

                                // Perform further collisions with the new bounds.
                                bounds = BoundingRectangle;
                            }

                            // Matthew: Handle collisions with drillable tiles, if we are not already drilling them
                            else if (collision == TileCollision.Drillable)
                            {
                                // Matthew: If drilling, destroy tile
                                if (isDrilling)
                                {
                                    // Matthew: set tile drilling flag
                                    isDrillingTile = true;

                                    // Matthew: Set initial drilling coordinates (used to calculate player speed)
                                    drillingStartPos = new Vector2(Position.X, Position.Y);

                                    // Matthew: Indicate drilling direction (used when calculating player speed)
                                    if (isRunningRight)
                                    {
                                        isDrillingRight = true;
                                    }
                                    else
                                    {
                                        isDrillingRight = false;
                                    }

                                    // Matthew: Destroy the drillable tile
                                    Level.DestroyTile(x, y);
                                }

                                // Matthew: ELSE impassable collision
                                else
                                {
                                    // Resolve the collision along the X axis.
                                    Position = new Vector2(Position.X + depth.X, Position.Y);

                                    // Perform further collisions with the new bounds.
                                    bounds = BoundingRectangle;
                                }
                            }
                        }
                    }
                }
            }

            // Save the new bounds bottom.
            previousBottom = bounds.Bottom;
        }

        /// <summary>
        /// Called when the player has been killed.
        /// </summary>
        /// <param name="killedBy">
        /// The enemy who killed the player. This parameter is null if the player was
        /// not killed by an enemy (fell into a hole).
        /// </param>
        public void OnKilled(Enemy killedBy)
        {
            isAlive = false;

            if (killedBy != null)
                killedSound.Play();
            else
                fallSound.Play();

            sprite.PlayAnimation(dieAnimation);
        }

        /// <summary>
        /// Called when this player reaches the level's exit.
        /// </summary>
        public void OnReachedExit()
        {
            sprite.PlayAnimation(celebrateAnimation);
        }

        /// <summary>
        /// Draws the animated player.
        /// </summary>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            // Draw only IF visible (toggles during damage sequence)
            if (visible)
            {
                // Flip the sprite to face the way we are moving.
                if (Velocity.X > 0)
                    flip = SpriteEffects.FlipHorizontally;
                else if (Velocity.X < 0)
                    flip = SpriteEffects.None;

                // Draw that sprite.
                sprite.Draw(gameTime, spriteBatch, Position, flip);
            }
            // Draw weapons
            weapons.Draw(spriteBatch);
        }

        // Update player life
        public void updateLife(int val)
        {
            life += val;

            // Cap life, it cannot go over MAX
            if (life > initLife)
            {
                life = initLife;
            }
        }
    }
}
